/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * vga.cc																	*
 *																			*
 * Puts stuff onto the screen.												*
 *																			*
\***************************************************************************/

#include <system.h>
#include <vga.h>
#include <memory.h>
#include <iostream>
#include <gui_txt.h>
#include <winman.h>

VGA::VGA()
{
	// set default LFB to 0xE0000000
	m_lfb = 0xE0000000;
	m_width = 800;
	m_height = 600;
	m_bpp = 32;
	m_bgcol = 0;
}

VGA::VGA( uint_t lfb )
{
	m_lfb = lfb;
}

VGA::~VGA()
{
	m_lfb = 0;
	kfree( (void*) m_dbuff );
}

void VGA::SetBgColor( unsigned int col )
{
	// set the background color
	m_bgcol = col;
}

uint_t VGA::GetXRes()
{
	// return the width
	return m_width;
}

uint_t VGA::GetYRes()
{
	// return the height
	return m_height;
}

uint_t VGA::GetBPP()
{
	// return the number of bits per pixel
	return m_bpp;
}

void VGA::EnterVGAMode( ushort_t xres, ushort_t yres, ushort_t bpp )
{
	// set the width, height and bpp
	m_width = xres;
	m_height = yres;
	m_bpp = bpp;
	
	// enter the mode (it returns the framebuffer address)
	m_lfb = EnterVesaMode( xres, yres, bpp );
	
	// setup the double buffer
	m_dbuff = (uint_t) kmalloc( m_width * m_height * ( m_bpp / 8 ) );
}

void VGA::ClearScreen()
{
	// pointer to the linear frame buffer
	unsigned int* lfb = (unsigned int*) m_dbuff;
	
	// colors
	unsigned int fillcol = m_bgcol;
	
	// clears the screen
	for( int i = 0; i < ( ( m_width * m_height * ( m_bpp / 8 ) ) / (sizeof(m_bgcol)/2)); i++ )
	{
		if( lfb[i] != fillcol )
			lfb[i] = fillcol;
	}
}

void VGA::Rectangle( uint_t x, uint_t y, uint_t w, uint_t h, uint_t color )
{
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			SetPixel( _x, _y, color );
		}
	}
}

void VGA::DrawBitmap( uchar_t* data, uint_t x, uint_t y, uint_t w, uint_t h, uint_t isbmp )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offset
	uint_t boffset;
	if( isbmp )
		boffset = w * h * 3;
	else
		boffset = 0;
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		if( isbmp )
		{
			for( int _x = (x+w); _x > x; _x-- )
			{
				// draw, if the x and y aren't too high
				if( _x < m_width && _y < m_height )
				{
					SetPixel( _x, _y, *((uint_t*) &data[boffset]) );
				}
				
				// decrement the offset
				boffset -= 3;
			}
		}
		else
		{
			for( int _x = x; _x < (x+w); _x++ )
			{
				// draw, if the x and y aren't too high
				if( _x < m_width && _y < m_height )
				{
					SetPixel( _x, _y, *((uint_t*) &data[boffset]) );
				}
				
				// increment the offset
				boffset += 3;
			}
		}
	}
}

// color-keyed bitmap draw
void VGA::DrawBitmap( uint_t* data, uint_t x, uint_t y, uint_t w, uint_t h, uint_t colorkey )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offset
	uint_t boffset = 0;
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			// draw, if the x and y aren't too high
			if( _x < m_width && _y < m_height )
			{
				if( data[boffset] != colorkey )
					SetPixel( _x, _y, data[boffset] );
			}
			
			// increment the bitmap offset
			boffset++;
		}
	}
}

void VGA::DrawBitmap( uint_t* data, uint_t x, uint_t y, uint_t w, uint_t h )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offset
	uint_t boffset = 0;
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			// draw, if the x and y aren't too high
			if( _x < m_width && _y < m_height )
			{
				SetPixel( _x, _y, data[boffset] );
			}
			
			// increment the bitmap offset
			boffset++;
		}
	}
}

// draws a cursor, then returns a pointer to the old data
uint_t* VGA::DrawCursor( uchar_t* data, uint_t x, uint_t y, uint_t w, uint_t h )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offsets
	uint_t offset = 0;
	uint_t boffset = 0;
	
	// return pointer
	uint_t* ret = (uint_t*) kmalloc( w * h );
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			// save the old data (we save all of it so we don't need special handling)
			ret[offset++] = *((uint_t*) m_dbuff + ( ( m_width * _y ) + _x ));
			
			// check if it's valid
			if( ( data[boffset] == 0xFE && data[boffset+1] == 0xFE && data[boffset+2] == 0xFE )
				||
				( data[boffset] == 0x00 && data[boffset+1] == 0x00 && data[boffset+2] == 0x00 )
			)
			{
				// draw, if the x and y aren't too high
				if( _x < m_width && _y < m_height )
				{					
					// set the new data
					SetPixel( _x, _y, *((uint_t*) &data[boffset]) );
				}
			}
			
			// increment the offset
			boffset += 3;
		}
	}
	
	// return the saved data
	return ret;
}

void VGA::SetPixel( uint_t x, uint_t y, uint_t color )
{
	// get the address
	uint_t* addr = (uint_t*) (m_dbuff + ( ( m_width * y ) + x ));
	
	// how many bits per pixel?
	switch( m_bpp )
	{
		// 8 bit
		case 8:
		
			// uchar_t is an 8-bit type
			*((uchar_t*) addr) = (uchar_t) color;
			
			break;
		
		// 16 bit
		case 16:
		
			// ushort_t is a 16-bit type
			*((ushort_t*) addr) = (ushort_t) color;
			
			break;
			
		// 24 bit
		case 24:
		
			// special handling
			*((uchar_t*) addr) = (uchar_t) color;
			*((uchar_t*) (addr+1)) = (uchar_t) ( color >> 8 );
			*((uchar_t*) (addr+2)) = (uchar_t) ( color >> 16 );
			
			break;
		
		// 32 bit
		case 32:
		
			// uint_t is 32 bits
			*((uint_t*) addr) = (uint_t) color;
			
			break;
			
	};
}

void VGA::DrawWindow( int x, int y, int w, int h, char* str, int len )
{
	// border :D
	Rectangle( x - 1, y - 1, w + 2, h + 2, COLOR_BLACK );
	
	// draw the window itself
	Rectangle( x, y, w, h, COLOR_WHITE );
	
	// draw the title bar
	Rectangle( x, y, w, 12, COLOR_RED );
	
	// draw the text
	DrawText( x + 2, y + 2, str, len, COLOR_WHITE );
}

void VGA::BlitToScreen()
{
	// vertical retrace
	while( inportb( 0x3DA ) & 0x08 );
	while( inportb( 0x3DA ) & 0x08 );
	
	// create pointers
	unsigned long long* lfb = (unsigned long long*) m_lfb;
	unsigned long long* dbuff = (unsigned long long*) m_dbuff;
	
	// running x and y counters
	uint_t x = 0, y = 0;
	
	// copy from the double buffer
	for( int i = 0; i < ( ( m_width * m_height * ( m_bpp / 8 ) ) / 8 ); i++ )
	{
		// check if they are not equal, if not, then we do need to copy
		if( lfb[i] != dbuff[i] )
			lfb[i] = dbuff[i];
		
		// increment x and y, if needed
		x += 8;
		if( x > m_width )
		{
			x = 0;
			y++;
		}
	}
}

void VGA::Line( uint_t x1, uint_t y1, uint_t x2, uint_t y2, uint_t color )
{
	// holds the choice
	int d;
	
	// dx/dy - differences
	int dx,dy;
	
	// variable increments - direction
	int Eincr, NEincr;
	
	// y increment
	int yincr;
	
	// counter
	int t;
	
	// TODO: factor this to the math file
	#define ABS(a)   ((a) >= 0 ? (a) : -(a))
	
	// get the slope
	dx = ABS( x2 - x1 );
	dy = ABS( y2 - y1 );
	
	// check
	if( dy <= dx )
	{
		// slope between -1 and 1
		if( x2 < x1 )
		{
			t = x2; x2 = x1; x1 = t; // swap x
			t = y2; y2 = y1; y1 = t; // swap y
		}
		
		// is the second higher than the first?
		if( y2 > y1 )
			yincr = 1;
		else
			yincr = -1;
		
		// initial decision
		d = 2 * dy - dx;
		Eincr = 2 * dy;
		NEincr = 2 * ( dy - dx );
		SetPixel( x1, y1, color );
		
		// incrementally determine postions of next pixels
		for( x1++; x1 <= x2; x1++ )
		{
			if( d < 0 )
			{
				d += Eincr;
			}
			else
			{
				d += NEincr;
				y1 += yincr;
			}
			
			// draw it
			SetPixel( x1, y1, color );
		}
	}
	else
	{
		// slope between -1 and 1 (??)
		if( y2 < y1 )
		{
			t = x2; x2 = x1; x1 = t; // swap x
			t = y2; y2 = y1; y1 = t; // swap y
		}
		
		// is the second higher than the first?
		if( x2 > x1 )
			yincr = 1;
		else
			yincr = -1;
		
		d = 2 * dx - dy;
		Eincr = 2 * dx;
		NEincr = 2 * ( dx - dy );
		SetPixel( x1, y1, color );
		
		// incrementally determine postions of next pixels
		for( y1++; y1 <= y2; y1++ )
		{
			if( d < 0 )
			{
				d += Eincr;
			}
			else
			{
				d += NEincr;
				x1 += yincr;
			}
			
			// draw it
			SetPixel( x1, y1, color );
		}
	}
}



/* sdl_picofont

   http://nurd.se/~noname/sdl_picofont

   File authors:
      Fredrik Hultin

   License: GPLv2
*/

// 8-bit font
#define FONTHEIGHT	8
#define FONTWIDTH	8

int VGA::DrawText( int x, int y, char* txt, int len, uint_t color )
{
	// get the font
	unsigned char* fnt = txtGetFont();
	
	// column and row
	unsigned int col = 0,row = 0;
	
	// block of pixels
	unsigned int pixels[64];
	unsigned char isdraw[64];
	
	// parse the string
	for( int i = 0; i < len; i++ )
	{
		// get the character
		unsigned char chr = txt[i];
		
		// handle special characters
		switch( chr )
		{
			case '\n':
				row++;
				col = 0;
				chr = 0;
				break;
			
			case '\r':
				chr = 0;
				break;
			
			case '\t':
				chr = 0;
				col += ( 4 - col % 4 );
				break;
			
			case '\0':
				chr = 0;
				break;

			default:
				col++;
				break;
		}
		
		// current offset into the pixel map
		int pxmap_off = 0;
		
		// check it
		if( chr != 0 )
		{
			// draw it!
			for( int _y = 0; _y < FONTHEIGHT; _y++ )
			{
				for( int _x = 0; _x < FONTWIDTH; _x++ )
				{
					// always start off by not drawing
					isdraw[pxmap_off] = 0;
					
					// check this byte
					if( fnt[ txt[i] * FONTHEIGHT + _y ] >> ( 7 - _x ) & 1 )
					{
						// plot this pixel
						pixels[pxmap_off] = color;
						isdraw[pxmap_off] = 1;
					}
					
					// increment the offset
					pxmap_off++;
				}
			}
		
			// reset the offset
			pxmap_off = 0;
			
			// blit this letter
			for( int py = y + ( row * 8 ); py < ( y + ( row * 8 ) ) + 8; py++ )
			{
				for( int px = x + ( col * 8 ); px < ( x + ( col * 8 ) ) + 8; px++ )
				{
					// plot it
					if( isdraw[pxmap_off] )
						SetPixel( px - 8, py, pixels[pxmap_off] );
					pxmap_off++;
				}
			}
		}
	}
}
